import getInterpolationData from './getInterpolationData';
import type { InterpolationViewportData, Annotation } from '../../../types';

/**
 * A pair of slice indices for contours, typically indicating the
 * pair of contours to interpolate between, or for a possible range to
 * compare against.
 */
export type ContourPair = [number, number];

/**
 * Finds the list of contours to interpolate,
 * including whether they are new contours, or auto generated contours that need
 * to be updated.
 *
 * @param toolData - object, The tool data of the roi contour.
 * @param viewportData - the annotation/viewport to start the interpolation from
 * @returns An object containing the interpolationData and the
 *       interpolationList.
 */

function findAnnotationsForInterpolation(
  toolData,
  viewportData: InterpolationViewportData
) {
  const interpolationData = getInterpolationData(viewportData, [
    {
      key: 'interpolationUID',
      value: viewportData.interpolationUID,
    },
  ]);
  const rangeToInterpolate = getRangeToInterpolate(interpolationData);
  if (!rangeToInterpolate) {
    console.warn('No annotations found to interpolate', interpolationData);
    return;
  }
  const sliceEdited = _getSlicePositionOfToolData(
    interpolationData,
    toolData.annotationUID
  );
  const interpolationList = [];

  // Check if contours between the extent can be interpolated.
  for (let i = rangeToInterpolate[0] + 1; i < rangeToInterpolate[1]; i++) {
    if (_sliceNeedsInterpolating(interpolationData, i)) {
      const contourPair = _getBoundingPair(
        i,
        rangeToInterpolate,
        interpolationData
      );

      if (
        contourPair?.[0] === sliceEdited ||
        contourPair?.[1] === sliceEdited
      ) {
        _appendInterpolationList(contourPair, interpolationList, i);
      }
    }
  }

  return {
    interpolationData,
    interpolationList,
  };
}

/**
 * Gets the range of interpolation data which has
 * annotations in it, as indices into the interpolationData
 *
 * @param interpolationData - Data on the slice location of contours to be
 *       interpolated.
 * @returns Number[], The slice locations of the top and bottom polygon of the ROIContour.
 */

function getRangeToInterpolate(
  interpolationData: Map<number, Annotation[]>
): ContourPair {
  let first = Infinity;
  let last = -Infinity;
  let found = false;

  for (const [sliceIndex, annotations] of interpolationData.entries()) {
    if (annotations.length) {
      first = Math.min(sliceIndex, first);
      last = Math.max(sliceIndex, last);
      found = true;
    }
  }
  if (!found) {
    return;
  }

  return [first, last];
}

/**
 * _getSlicePositionOfToolData - Finds the slice that was edited.
 *
 * @param interpolationData - Data on the slice location of contours to be
 *       interpolated.
 * @param annotationUID - the UID to find the slice position for
 * @returns index in the interpolationData containing that annotationUID
 */
function _getSlicePositionOfToolData(
  interpolationData: Map<number, Annotation[]>,
  annotationUID: string
): number {
  for (const [sliceIndex, annotations] of interpolationData) {
    for (let j = 0; j < annotations.length; j++) {
      if (annotations[j].annotationUID === annotationUID) {
        return sliceIndex;
      }
    }
  }

  return;
}

/**
 * Slices need interpolation when either:
 *   * There are no contours on this slice
 *   * There is a contour which is an interpolated contour.
 *
 * @param interpolationData - Data on the slice location of contours to be
 *       interpolated.
 * @param sliceIndex - The slice index.
 * @returns Whether or not the slice needs interpolating.
 */
function _sliceNeedsInterpolating(
  interpolationData: Map<number, Annotation[]>,
  sliceIndex: number
): boolean {
  const annotations = interpolationData.get(sliceIndex);
  return (
    !annotations?.length ||
    (annotations.length === 1 && annotations[0].autoGenerated)
  );
}

/**
 * If the contour on slice i can be updated, add it to the
 * interpolationList.
 *
 * @param contourPair - the pair of items to append to
 * @param interpolationList - The list of contours to be interpolated.
 * @param itemIndex - the item index to append data to
 */
function _appendInterpolationList(contourPair, interpolationList, itemIndex) {
  const [startIndex] = contourPair;
  interpolationList[startIndex] ||= {
    pair: contourPair,
    list: [],
  };

  interpolationList[startIndex].list.push(itemIndex);
}

/**
 * _getBoundingPair - Given the slice index and range of indices to apply the contour to,
 * get the pair of polygons to use for interpolation of the slice.
 * Returns undefined if there is an ambiguity and interpolation can't take place.
 *
 * @param sliceIndex - The slice index.
 * @param sliceRange - The extent of slice occupancy of the ROIContour.
 * @param interpolationData - Data on the slice location of contours to be
 *       interpolated.
 * @returns The pair of slice indices, or undefined if
 *       the contours to use for interpolation is ambiguous.
 */

function _getBoundingPair(
  sliceIndex: number,
  sliceRange: ContourPair,
  interpolationData: Map<number, Annotation[]>
): ContourPair {
  const annotationPair = [];
  let canInterpolate = true;

  // Check for nearest lowest sliceIndex containing contours.
  for (let i = sliceIndex - 1; i >= sliceRange[0]; i--) {
    const annotations = interpolationData.get(i);
    if (annotations?.length) {
      if (annotations[0].autoGenerated) {
        // This contour is interpolated. We need to find a solid contour to
        // interpolate from.
        continue;
      }

      if (annotations.length > 1) {
        canInterpolate = false;
      }

      // Found single, non interpolated contour to interpolate from.
      annotationPair.push(i);
      break;
    }
  }

  if (!canInterpolate || !annotationPair.length) {
    return;
  }

  // Check for nearest upper sliceIndex containing contours.
  for (let i = sliceIndex + 1; i <= sliceRange[1]; i++) {
    const annotations = interpolationData.get(i);
    if (annotations?.length) {
      if (annotations[0].autoGenerated) {
        // This contour is interpolated. We need to
        // Find a solid contour to interpolate from.
        continue;
      }

      if (annotations.length > 1) {
        canInterpolate = false;
      }

      annotationPair.push(i);
      break;
    }
  }

  if (!canInterpolate || annotationPair.length < 2) {
    return;
  }

  return annotationPair as ContourPair;
}

export default findAnnotationsForInterpolation;
